﻿using Microsoft.EntityFrameworkCore;
using Temporalio.Client.Schedules;
using Temporalio.Client;

namespace APMService
{
    public interface IAutoPreventiveMaintenanceService
    {
        Task<APMGroup> CreateGroup(OperateGroupRequest req);

        Task TestWorkflow(int group_id);

        Task<APMGroup?> GetGroup(int group_id);

        Task<bool> UpdateGroup(OperateGroupRequest req);

        Task<bool> DeleteGroup(int group_id);

        Task<GroupsResponse> GetAllGroups();
    }
    public class AutoPreventiveMaintenanceService : IAutoPreventiveMaintenanceService
    {
        private readonly TemporalClient _client;
        private readonly IDbContextFactory<AppDbContext> _dbContextFactory;

        private readonly ILogger<AutoPreventiveMaintenanceService> _logger;

        public AutoPreventiveMaintenanceService(TemporalClient client, IDbContextFactory<AppDbContext> dbContextFactory, ILogger<AutoPreventiveMaintenanceService> logger)
        {
            _client = client;
            _dbContextFactory = dbContextFactory;
            _logger = logger;
        }

        public async Task<APMGroup?> GetGroup(int group_id)
        {
            using var dbContext = _dbContextFactory.CreateDbContext();
            var group = await dbContext.APMGroups.Include(g => g.APMContexts)
                .FirstOrDefaultAsync(g => g.Id == group_id);
            return group;
        }

        /// <summary>
        /// 根据传入的TriggerTime, Frequency, Day 构造Cron表达式
        /// </summary>
        /// <param name="group"></param>
        /// <returns></returns>
        /// <exception cref="ArgumentException"></exception>
        string GetCron(APMGroup group)
        {
            var utcTime = DateTime.Parse(group.TriggerTime);
            _logger.LogInformation($"TriggerTime(utc) {utcTime}");

            int utcHour = utcTime.Hour;
            int utcMinute = utcTime.Minute;

            string cron;

            switch (group.TaskFrequency)
            {
                case Frequency.Daily:
                    cron = $"{utcMinute} {utcHour} * * *";
                    break;
                case Frequency.Weekly:
                    if (group.Day < 1 || group.Day > 7)
                    {
                        throw new ArgumentException("Day must be between 1 (Sunday) and 7 (Saturday) for weekly tasks.");
                    }
                    cron = $"{utcMinute} {utcHour} * * {group.Day}";
                    break;
                case Frequency.Monthly:
                    if (group.Day < 1 || group.Day > 31)
                    {
                        throw new ArgumentException("Day must be between 1 and 31 for monthly tasks.");
                    }
                    cron = $"{utcMinute} {utcHour} {group.Day} * *";
                    break;
                default:
                    throw new ArgumentException("Unsupported frequency type.");
            }

            _logger.LogInformation($"cron: {cron}");
            return cron;
        }

        public async Task<ScheduleHandle?> CreateSchedule(APMGroup group)
        {

            var contexts = group.APMContexts;
            var cron = GetCron(group);
            string scheduleId = $"group-{group.Id}";

            var list = new List<int>();

            using var dbContext = _dbContextFactory.CreateDbContext();
            foreach (var context in contexts)
            {
                var originalContext = await dbContext.APMContexts.FirstOrDefaultAsync(c => c.GroupId == context.GroupId && c.ConditionNo == context.ConditionNo && c.Action == context.Action);
                if (originalContext != null)
                {
                    list.Add(originalContext.Id);
                }
            }
            if (list == null || list.Count == 0) return null;


            Schedule schedule = new(Action: ScheduleActionStartWorkflow.Create((APMWorkflow wf) => wf.RunAsync(list),
            new(id: scheduleId, taskQueue: "apm-queue")),
            Spec: new()
            {
                StartAt = DateTime.UtcNow,
                CronExpressions = [cron]
            });

            var handle = await _client.CreateScheduleAsync(scheduleId, schedule);
            if (!group.Enable)
            {
                await handle.PauseAsync();
            }
            _logger.LogInformation($"Create Schedule with ID: {handle.Id}");
            return handle;
        }

        public async Task TestWorkflow(int group_id)
        {
            var group = GetGroup(group_id);
            if (group != null)
            {
                var scheduleId = $"group-{group_id}";
                var handle = _client.GetScheduleHandle(scheduleId);
                await handle.TriggerAsync();
            }
        }


        public async Task<APMGroup> CreateGroup(OperateGroupRequest req)
        {
            _logger.LogInformation($"Start create group_id: {req.GroupId}, Group: {req.Group}...");
            ScheduleHandle? handle = null;
            using var dbContext = _dbContextFactory.CreateDbContext();

            var group = req.Group;
            var contexts = group.APMContexts;

            await dbContext.APMGroups.AddAsync(group);
            await dbContext.SaveChangesAsync();

            handle = await CreateSchedule(group);

            return group;
        }

        async Task UpdateAPMContexts(APMGroup srcGroup, APMGroup destGroup, AppDbContext dbContext)
        {
            var processedIds = new HashSet<int>();

            foreach (var updateContext in destGroup.APMContexts)
            {
                updateContext.GroupId = srcGroup.Id;

                if (updateContext.LastRunTime == default)
                {
                    srcGroup.APMContexts.Add(updateContext);
                }
                else
                {
                    var originalContext = await dbContext.APMContexts
                        .FirstOrDefaultAsync(c => c.GroupId == updateContext.GroupId && c.ConditionNo == updateContext.ConditionNo && c.Action == updateContext.Action);

                    if (originalContext == null)
                    {
                        srcGroup.APMContexts.Add(updateContext);
                    }
                    else
                    {
                        originalContext.Oridinal = updateContext.Oridinal;
                        originalContext.ConditionNo = updateContext.ConditionNo;
                        originalContext.ConditionDescription = updateContext.ConditionDescription;
                        originalContext.Action = updateContext.Action;
                        originalContext.Threshold = updateContext.Threshold;
                        originalContext.Comments = updateContext.Comments;
                        processedIds.Add(originalContext.Id);
                    }
                }
            }

            var idsToRemove = srcGroup.APMContexts
                .Where(c => !processedIds.Contains(c.Id))
                .Select(c => c.Id)
                .ToList();

            foreach (var id in idsToRemove)
            {
                var contextToRemove = await dbContext.APMContexts.FindAsync(id);
                if (contextToRemove != null)
                {
                    dbContext.APMContexts.Remove(contextToRemove);
                }
            }
        }

        async Task UpdateDbGroup(APMGroup src, APMGroup dest, AppDbContext dbContext)
        {
            src.Name = dest.Name;
            src.TaskFrequency = dest.TaskFrequency;
            src.Day = dest.Day;
            src.TriggerTime = dest.TriggerTime;
            src.Updated = dest.Updated;
            src.Comments = dest.Comments;
            src.Enable = dest.Enable;
            await UpdateAPMContexts(src, dest, dbContext);
            await dbContext.SaveChangesAsync();
        }

        public async Task<ScheduleHandle?> UpdateSchedule(APMGroup group)
        {

            var contexts = group.APMContexts;
            var cron = GetCron(group);
            string scheduleId = $"group-{group.Id}";

            var list = new List<int>();

            using var dbContext = _dbContextFactory.CreateDbContext();
            foreach (var context in contexts)
            {
                var originalContext = await dbContext.APMContexts.FirstOrDefaultAsync(c => c.GroupId == context.GroupId && c.ConditionNo == context.ConditionNo && c.Action == context.Action);
                if (originalContext != null)
                {
                    list.Add(originalContext.Id);
                }
            }
            if (list == null || list.Count == 0) return null;


            Schedule schedule = new(Action: ScheduleActionStartWorkflow.Create((APMWorkflow wf) => wf.RunAsync(list),
            new(id: scheduleId, taskQueue: "apm-queue")),
            Spec: new()
            {
                StartAt = DateTime.UtcNow,
                CronExpressions = [cron]
            });

            var handle = _client.GetScheduleHandle(scheduleId);
            await handle.UpdateAsync(_ => new(schedule));
            if (!group.Enable)
            {
                await handle.PauseAsync();
            }
            _logger.LogInformation($"Create Schedule with ID: {handle.Id}");
            return handle;
        }


        public async Task<bool> UpdateGroup(OperateGroupRequest req)
        {
            _logger.LogInformation($"Start update group_id: {req.GroupId}, Group: {req.Group}...");
            using var dbContext = _dbContextFactory.CreateDbContext();

            var group = req.Group;
            APMGroup? originalGroup = null;

            originalGroup = await dbContext.APMGroups
                .Include(g => g.APMContexts)
                .FirstOrDefaultAsync(g => g.Id == group.Id);

            if (originalGroup == null)
                return false;

            await UpdateDbGroup(originalGroup, group, dbContext);
            await UpdateSchedule(group);
            return true;
        }

        public async Task<bool> DeleteGroup(int group_id)
        {
            _logger.LogInformation($"delete group_id: {group_id}");
            using var dbContext = _dbContextFactory.CreateDbContext();
            ScheduleHandle? handle = null;

            var originalGroup = await dbContext.APMGroups.FindAsync(group_id);
            if (originalGroup == null)
            {
                _logger.LogInformation($"{group_id} not found");

                return false;
            }

            dbContext.APMGroups.Remove(originalGroup);
            await dbContext.SaveChangesAsync();

            if (originalGroup != null)
            {
                handle = _client.GetScheduleHandle($"group-{group_id}");
                await handle.DeleteAsync();
            }
            return true;

        }

        public async Task<GroupsResponse> GetAllGroups()
        {
            _logger.LogInformation($"query all groups ...");
            using var dbContext = _dbContextFactory.CreateDbContext();
            var groups = await dbContext.APMGroups.Include(g => g.APMContexts).ToListAsync();
            _logger.LogInformation($"groups: {groups}");
            var result = new GroupsResponse
            {
                Groups = groups
            };
            return result;
        }
    }
}
